package com.openess.bigsearch.engine.index.merge;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;

import com.openess.bigsearch.engine.BPlusTree.BPlusTree;
import com.openess.bigsearch.engine.BPlusTree.BPlusTree.Post;
import com.openess.bigsearch.engine.BPlusTree.StringBKCompare;
import com.openess.bigsearch.engine.BTree.util.Arrays;
import com.openess.bigsearch.engine.BTree.util.Arrays.Sort;
import com.openess.bigsearch.engine.index.Extent;
import com.openess.bigsearch.engine.index.Extent.ExtData;
import com.openess.bigsearch.engine.index.FrqPos;
import com.openess.bigsearch.engine.index.Nrm;
import com.openess.bigsearch.engine.index.Nrm.NrmData;
import com.openess.bigsearch.engine.utils.HashMap;

/**
 * 索引的合并类，合并三类文件:1.主索引。2.Nrm索引。3.Extent索引。(segment不参加合并)
 * 
 * @author TuTu
 * 
 */
public class Merge {

	/**
	 * 合并全部子索引
	 * 
	 * @param indexPath
	 * @param version
	 * @throws IOException
	 */
	public static void merge(String indexPath, int version) throws IOException {
		if (version == 1)
			return;
		mergeIndex(indexPath, version);
		mergeNrm(indexPath, version);
		mergeExtent(indexPath, version);
	}

	/**
	 * 合并B+树主索引，采用多路归并算法(败者树).<br>
	 * 这种合并方式的有点在于，败者树效率高，每次pop耗时O(lgk).<br>
	 * 多路合并数据属于外排序， 同一时刻内存中只会装在k个节点的数据.
	 * 
	 * @param indexPath
	 * @param version
	 *            版本号，不能随便起，从0-version-1必须都有对应的索引
	 * @throws IOException
	 */
	protected static void mergeIndex(String indexPath, int version)
			throws IOException {
		// 新的整合后的主索引
		BPlusTree newtree = new BPlusTree(indexPath, new StringBKCompare(),
				version);
		ArrayList<Iterator<Post>> list = new ArrayList<Iterator<Post>>(version);
		// 待合并的子索引
		for (int i = 0; i < version; i++) {
			BPlusTree tree = new BPlusTree(indexPath, new StringBKCompare(), i);
			list.add(tree.posts().iterator());
		}
		// 败者树的多路排序/合并
		LoserTree<Post> lt = new LoserTree<Post>(list);
		Post p = null;
		Post pre = lt.pop();
		// 记录读取了多少倒排表
		int count = 0;
		while ((p = lt.pop()) != null) {
			count++;
			if (count % 100 == 0)
				System.out.println("readPost:" + count);
			// 合并相同的倒排表
			while (p != null && pre.compareTo(p) == 0) {
				pre = mergeFrqPos(pre, p);
				p = lt.pop();
				count++;
				if (count % 100 == 0)
					System.out.println("readPost:" + count);
			}
			newtree.insertFromRoot(pre.getTerm(), pre.getPos());
			pre = p;
		}
		// 插入遗漏的最后一条记录
		if (pre != null)
			newtree.insertFromRoot(pre.getTerm(), pre.getPos());
	}

	/**
	 * 合并p1和p2，合并其中的FrqPos，因为是建索引，所以不会出现id重复的现象，仅需按id排序即可
	 * 
	 * @param p1
	 * @param p2
	 */
	private static Post mergeFrqPos(Post p1, Post p2) {
		byte[] term = p1.getTerm();
		FrqPos[] pos1 = p1.getPos();
		FrqPos[] pos2 = p2.getPos();
		FrqPos[] newpos = new FrqPos[pos1.length + pos2.length];
		System.arraycopy(pos1, 0, newpos, 0, pos1.length);
		System.arraycopy(pos2, 0, newpos, pos1.length, pos2.length);
		Arrays.sort(newpos, Sort.HEAP);
		return new Post(term, newpos);
	}

	/**
	 * 合并nrm表
	 * 
	 * @param version
	 *            版本号，不能随便起，从0-version-1必须都有对应的索引
	 * @throws IOException
	 */
	protected static void mergeNrm(String indexPath, int version)
			throws IOException {
		// 整合后的nrm
		Nrm newnrm = new Nrm(indexPath, "w", version);
		HashMap<Long, NrmData> newmap = newnrm.getMap();
		int docNum = 0;
		int totalLength = 0;
		for (int i = 0; i < version; i++) {
			Nrm nrm = new Nrm(indexPath, "rw", i);
			docNum += nrm.getDocNum();
			totalLength += nrm.getTotalLength();
			newmap.putAll(nrm.getMap());
		}
		newnrm.setDocNum(docNum);
		newnrm.setTotalLength(totalLength);
		newnrm.write();
	}

	/**
	 * 合并Extent表
	 * 
	 * @param version
	 *            版本号，不能随便起，从0-version-1必须都有对应的索引
	 * @throws IOException
	 */
	protected static void mergeExtent(String indexPath, int version)
			throws IOException {
		Extent newext = new Extent(indexPath, "w", version);
		HashMap<Long, Integer[]> newmap = newext.getMainExtMap();
		for (int i = 0; i < version; i++) {
			Extent ext = new Extent(indexPath, "r", i);
			String[] fields = ext.getFieldList();
			for (String f : fields) {
				ExtData[] dataList = ext.readExtValue(f);
				for (ExtData d : dataList)
					newext.addExtent(f, d.getDocID(), d.getStart(), d.getEnd());
			}
			newmap.putAll(ext.getMainExtMap());
		}
		newext.writeExtent();
	}
}
